package comms;

import ap.*;
import agent.*;
import java.util.Vector;
import java.util.Enumeration;

/**
 * ConnectionManager class which acts as a central object which creates and 
 * handles incoming and outgoing agents at the byte/protocol level.
 * 
 * @author Kevin
 */
public class ConnectionManager {
	private String WK_UDPPORT = "57353";
	private String WK_TCPPORT = "57354";
	//private static final String WK_UDPPORT = "57357";
	//private static final String WK_TCPPORT = "57358";
	private String UDPURI_IN = "datagram://:";
	private String TCPURI_IN = "socket://:";
	private AgentPlatform agentPlatform;
	private Vector currentConnections;
	private Vector currentURIs;

	/**
	 * Simple constructor which instantiates the required objects.
	 * 
	 * Constructor also creates connections on the application specific ports
	 * which will always listen for incoming agents.
	 * 
	 * @param p
	 */
	public ConnectionManager(AgentPlatform p, String udpPort, String tcpPort) {
		this.WK_UDPPORT = udpPort;
		this.UDPURI_IN += udpPort;
		this.WK_TCPPORT = tcpPort;
		this.TCPURI_IN += tcpPort;
		this.agentPlatform = p;
		this.currentConnections = new Vector(0);
		this.currentURIs = new Vector(0);
		
		try {
			this.listenForAgent(this.UDPURI_IN);
			this.listenForAgent(this.TCPURI_IN);
			//TODO:Need to listen for an MMS connection
			//this.listenForAgent(some MMS URI);
		} catch (URIException e) {
			System.err.println(e.getMessage());
			e.printStackTrace();
		}
	}
	
	/**
	 * Private method which creates and returns an AgentConnection object based
	 * on the passed URI String.
	 * 
	 * @param uri The URI String for the connection to be opened and returned.
	 * @return The created connection.
	 * @throws URIException
	 */
	private AgentConnection getAgentConnection(String uri) throws URIException {
		AgentConnection connection = null;
		String protocol = URIParser.getURIProtocol(uri);
		
		if (protocol.equals("datagram")) {
			connection = new DatagramAgentConnection(this, URIParser.getURIAddress(uri), URIParser.getURIPortAsString(uri));
		} else if (protocol.equals("socket")) {
			connection = new SocketAgentConnection(this, URIParser.getURIAddress(uri), URIParser.getURIPortAsString(uri));
		} else if (protocol.equals("mms")) {
			connection = new MMSAgentConnection(this, URIParser.getURIAddress(uri), URIParser.getURIPortAsString(uri));
		} else {
			throw new URIException("Incorrect or unknown protocol type.");
		}
		
		return connection;
	}
	
	/**
	 * Overloaded method for forwarding/sending an Agent to another device/address.
	 * 
	 * @param agent The Agent to be sent.
	 * @param uri The URI representing the destination device.
	 * @param keepOpen Should the connection be kept alive after sending.
	 * @throws URIException
	 */
	public synchronized void forwardAgent(MeetingAgent agent, String uri, boolean keepOpen) throws URIException {
		AgentConnection connection = null;
		
		connection = this.getAgentConnection(uri);
		
		if (connection != null) {
			try {
				connection.sendAgent(agent);
			} catch (AgentConnectionException x) {
				System.err.println(x.getMessage());
				x.printStackTrace();
			}
		}
		
		if (keepOpen) {
			this.currentConnections.addElement(connection);
			this.currentURIs.addElement(uri);
		} else {
			connection.closeConnection();
		}
	}
	
	/**
	 * Overloaded method for forwarding/sending an Agent to another device/address.
	 * 
	 * Method iterates through the currently maintained connections and sends the agent
	 * via all open connections.
	 * 
	 * @param agent The Agent to be sent.
	 */
	public synchronized void forwardAgent(MeetingAgent agent) {	
		AgentConnection connection = null;
		
		for (Enumeration e = this.currentConnections.elements(); e.hasMoreElements(); ) {
			try {
				connection = (AgentConnection)e.nextElement();
				if ((connection != null) && ((connection.getStatus() == AgentConnection.SENDER_ONLY) || (connection.getStatus() == AgentConnection.SENDER_RECEIVER))) {
					connection.sendAgent(agent);
				}
			} catch (AgentConnectionException x) {
				System.err.println(x.getMessage());
				x.printStackTrace();
			}
		}
	}
	
	/**
	 * Overloaded method for forwarding/sending an Agent to another device/address.
	 * 
	 * Method calls the get methods within the AgentPkt object to determine
	 * the address and type of connection to send the agent to.
	 * 
	 * @param agentPack The AgentPkt object representing the Agent to be sent.
	 */
	public synchronized void forwardAgent(AgentPkt agentPack) {
		AgentConnection connection = null;
		StringBuffer uri = new StringBuffer(0);
		
	/*	if (agentPack.getAddressType() == AgentPkt.TYPE_DATAGRAM) {
			uri.append("datagram://");
		} else if (agentPack.getAddressType() == AgentPkt.TYPE_SOCKET) {
			uri.append("socket://");
		}
		*/
		uri.append(agentPack.getDestAddress() + agentPack.getDestPort());
		//uri.append(agentPack.getDest());
		
		try {
			connection = this.getAgentConnection(uri.toString());
			if ((connection != null) && ((connection.getStatus() == AgentConnection.SENDER_ONLY) || (connection.getStatus() == AgentConnection.SENDER_RECEIVER))) {
				connection.sendAgent(agentPack);
				//TODO: verify connection is closed.
				//Should be closing the connection here but I'm unsure if it's being done elsewhere?
			}
		} catch (AgentConnectionException x) {
			System.err.println(x.getMessage());
			x.printStackTrace();
		} catch (URIException e) {
			System.err.println(e.getMessage());
			e.printStackTrace();
		}		
	}
	/*******************************************************************************************************************************************/
	/**
	 * Overloaded method for forwarding/sending an Agent to another device/address.
	 * 
	 * @param agentPack The AgentPkt object representing the Agent to be sent.
	 * @param uri The URI representing the destination device.
	 * @param keepOpen Should the connection be kept alive after sending.
	 * @throws URIException
	 */
	public synchronized void forwardAgent(AgentPkt agentPack, String uri, boolean keepOpen) throws URIException {
		AgentConnection connection = null;
		
		connection = this.getAgentConnection(uri);
		
		if (connection != null) {
			try {
				connection.sendAgent(agentPack);
			} catch (AgentConnectionException x) {
				System.err.println(x.getMessage());
				x.printStackTrace();
			}
		}
		
		if (keepOpen) {
			this.currentConnections.addElement(connection);
			this.currentURIs.addElement(uri);
		} else {
			connection.closeConnection();
		}

	}
	
	/**
	 * Overloaded method for forwarding/sending an Agent to another device/address.
	 * 
	 * Method sends the supplied Agent via the application specific TCP port to the supplied
	 * address.
	 * 
	 * @param agentPack The AgentPkt object representing the Agent to be sent.
	 */
	public synchronized void forwardAgent(AgentPkt agentPack, String address) {
		AgentConnection connection = null;
		
		try {
			connection = this.getAgentConnection("socket://" + address + ":" + WK_TCPPORT);
		} catch (URIException e) {
			System.err.println(e.getMessage());
			e.printStackTrace();
		}
		
		if (connection != null) {
			try {
				connection.sendAgent(agentPack);
				connection.closeConnection();
			} catch (AgentConnectionException x) {
				System.err.println(x.getMessage());
				x.printStackTrace();
			}
		}
	}
	
	/**
	 * Overloaded method for forwarding/sending an Agent to another device/address.
	 * 
	 * Method sends the supplied Agent via the application specific TCP port to the supplied
	 * address.
	 * 
	 * @param agent The Agent object representing the Agent to be sent.
	 */
	public synchronized void forwardAgent(MeetingAgent agent, String address) {
		AgentConnection connection = null;
		
		try {
			connection = this.getAgentConnection("socket://" + address + ":" + WK_TCPPORT);
		} catch (URIException e) {
			System.err.println(e.getMessage());
			e.printStackTrace();
		}
		
		if (connection != null) {
			try {
				connection.sendAgent(agent);
				connection.closeConnection();
			} catch (AgentConnectionException x) {
				System.err.println(x.getMessage());
				x.printStackTrace();
			}
		}
	}
	
	/**
	 * Listen for an Agent on the supplied URI connection.
	 * 
	 * @param uri The URI representing the connection which will listen for agents.
	 * @throws URIException
	 */
	public synchronized void listenForAgent(String uri) throws URIException {
		AgentConnection connection = null;
		connection = this.getAgentConnection(uri);
		
		if (connection != null) {
			this.currentConnections.addElement(connection);
			this.currentURIs.addElement(uri);
		}
	}
	
	/**
	 * Halt listening on the supplied URI connection.
	 * 
	 * Has no effect if the URI does not represent an active/open connection.
	 * 
	 * @param uri The URI representing the connection which will cease listening for agents.
	 */
	public synchronized void haltListeningForAgent(String uri) {
		AgentConnection connection = null;
		
		for (int i = 0; i < this.currentURIs.size(); i++) {
			if (((String)this.currentURIs.elementAt(i)).equals(uri)) {
				this.currentURIs.removeElementAt(i);
				connection = (AgentConnection)this.currentConnections.elementAt(i);
				this.currentConnections.removeElementAt(i);
				connection.closeConnection();
			}
		}		
	}
	
	/**
	 * Called by AgentConnection objects when an Agent has been received. Passes the
	 * received agentString on to an AgentPlatform object for further processing.
	 * 
	 * @param data The Byte data extracted from the connection.
	 * @param dataLength The length of the meaningful data.
	 */
	public synchronized void agentReceived(byte[] data, int dataLength) {
		String agentString = new String(data, 0, dataLength);
		this.agentPlatform.setInAgent(agentString);
	}
	
	/**
	 * Stops the ConnectionManager and closes all current connections.
	 *
	 */
	public synchronized void stop() {
		for (Enumeration e = this.currentConnections.elements(); e.hasMoreElements(); ) {
			((AgentConnection)e.nextElement()).closeConnection();
		}
	}
}
